﻿using OpenApi.OAuths.Datas.DbModels;
using OpenApi.OAuths.Datas.DbServices;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.ServiceModel.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace OpenApi.OAuths
{
    public class OauthComponent : OAuthBase
    {
        public readonly static OauthComponent Instance;

        /// <summary>
        /// 必要的Oauth相关的参数信息
        /// </summary>
        private static string[] RequireOauthParameters;

        /// <summary>
        /// The OAuth credentials prefix（前缀） for the Authorization HTTP header
        /// </summary>
        public static readonly Regex OAuthCredentialsRegex;

        /// <summary>
        /// String escape sequences
        /// </summary>
        public static readonly Regex StringEscapeSequence;

        /// <summary>
        /// The Authorization HTTP header parameter
        /// </summary>
        public const string AuthorizationHeaderParameter = "Authorization";


        /// <summary>
        /// 静态构造函数
        /// </summary>
        static OauthComponent()
        {
            RequireOauthParameters = new string[] { 
                OAuthConsumerKeyKey,
                OAuthSignatureMethodKey, 
                OAuthSignatureKey,
                OAuthTimestampKey, 
                OAuthNonceKey, 
                OAuthVersionKey 
            };
            OAuthCredentialsRegex = new Regex(@"^OAuth\s+", RegexOptions.Compiled | RegexOptions.IgnoreCase);
            StringEscapeSequence = new Regex(@"\\([""'\0abfnrtv]|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]+)", RegexOptions.Compiled);
            Instance = new OauthComponent();
        }

        /// <summary>
        /// 用户私有资源的授权
        /// </summary>
        public string PrivateResourceAuthorization(IncomingWebRequestContext incomingWebRequestContext)
        {
            UriTemplateMatch uriTemplateMatch = incomingWebRequestContext.UriTemplateMatch;
            Dictionary<string, string> oauthParameters = this.GetOauthParameters(uriTemplateMatch.QueryParameters);
            if (oauthParameters.Count == 0)
            {
                oauthParameters = this.GetOauthParameters(ParseAuthHeader(incomingWebRequestContext.Headers[AuthorizationHeaderParameter]));
            }
            this.CheckParameters(oauthParameters);
            this.CheckRequestId(oauthParameters);
            this.CheckSigningMethod(oauthParameters);
            this.CheckConsumer(oauthParameters);
            this.CheckToken(oauthParameters);
            AccessToken at = AccessTokenDbService.Instance.GetAccessToken(oauthParameters[OAuthTokenKey]);
            if (at.Status == 1)
            {
                throw new ArgumentException("Token已失效", OAuthTokenKey);
            }
            this.CheckSignature(oauthParameters, uriTemplateMatch.RequestUri, at.Token, at.Secret);
            return at.UserId;
        }

        /// <summary>
        /// 公共资源的授权
        /// </summary>
        /// <param name="context">当前Http请求操作的上下文</param>
        public void PublicResourceAuthorization(IncomingWebRequestContext incomingWebRequestContext)
        {
            UriTemplateMatch uriTemplateMatch = incomingWebRequestContext.UriTemplateMatch;
            Dictionary<string, string> oauthParameters = this.GetOauthParameters(uriTemplateMatch.QueryParameters);
            if (oauthParameters.Count == 0)
            {
                oauthParameters = this.GetOauthParameters(ParseAuthHeader(incomingWebRequestContext.Headers[AuthorizationHeaderParameter]));
            }
            this.CheckParameters(oauthParameters);
            this.CheckRequestId(oauthParameters);
            this.CheckSigningMethod(oauthParameters);
            this.CheckConsumer(oauthParameters);
            this.CheckSignature(oauthParameters, uriTemplateMatch.RequestUri, null, null);
        }

        /// <summary>
        /// 获取OAuth相关的参数信息
        /// </summary>
        /// <param name="queryParameters">查询字符串相关</param>
        /// <returns></returns>
        private Dictionary<string, string> GetOauthParameters(NameValueCollection queryParameters)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (var name in queryParameters.AllKeys)
            {
                if (name.StartsWith("oauth_"))
                {
                    result.Add(name, queryParameters[name]);
                }
            }
            return result;
        }

        /// <summary>
        /// 初始化当前请求所需要的各种参数（不能包含额外的参数）；
        /// </summary>
        private void CheckParameters(Dictionary<string, string> oauthParameters)
        {
            foreach (var k in RequireOauthParameters)
            {
                if (!oauthParameters.ContainsKey(k) || oauthParameters.ContainsKey(k) && string.IsNullOrEmpty(oauthParameters[k]))
                {
                    throw new ArgumentNullException(k, this.GetArgumentExceptionMessage(k));
                }
            }
        }

        /// <summary>
        /// 设置Oauth当前签名提供应用程序
        /// </summary>
        /// <param name="oauthParameters">OAuth相关的参数信息</param>
        private void CheckSigningMethod(Dictionary<string, string> oauthParameters)
        {
            if (!oauthParameters[OAuthBase.OAuthSignatureMethodKey].Equals(HMACSHA1SignatureType))
            {
                throw new ArgumentException("不支持该签名方法", oauthParameters[OAuthBase.OAuthSignatureMethodKey]);
            }
        }

        /// <summary>
        /// 设置当前用户信息
        /// </summary>
        private void CheckConsumer(Dictionary<string, string> oauthParameters)
        {
            OAuthConsumer customer = OAuthConsumerDbService.Instance.GetCustomer(oauthParameters[OAuthBase.OAuthConsumerKeyKey]);
            if (customer == null)
            {
                throw new ArgumentException("第三方应用无效", OAuthBase.OAuthConsumerKeyKey);
            }
        }

        /// <summary>
        /// 设置当前请求的验证处理
        /// </summary>
        private void CheckRequestId(Dictionary<string, string> oauthParameters)
        {
            long now = Convert.ToInt64(this.GenerateTimeStamp());
            long timestamp;
            if (!long.TryParse(oauthParameters[OAuthBase.OAuthTimestampKey], out timestamp)
              || timestamp <= 0
              || (now - timestamp) > 120)
            {
                throw new ArgumentException("请求已过期", OAuthBase.OAuthTimestampKey);
            }
        }

        /// <summary>
        /// 设置当前的请求令牌
        /// </summary>
        private void CheckToken(Dictionary<string, string> oauthParameters)
        {
            if (oauthParameters.ContainsKey(OAuthTokenKey))
            {
                if (!AccessTokenDbService.Instance.ExistsAccessToken(oauthParameters[OAuthTokenKey]))
                {
                    throw new ArgumentNullException(OAuthTokenKey, "Token无效");
                }
            }
            else
            {
                throw new ArgumentException(this.GetArgumentExceptionMessage(OAuthTokenKey), OAuthTokenKey);
            }
        }

        /// <summary>
        /// 设置当前的签名（并且检查其正确性）
        /// </summary>
        private void CheckSignature(Dictionary<string, string> oauthParameters, Uri uri, string token, string tokenSecret)
        {
            OAuthConsumer customer = OAuthConsumerDbService.Instance.GetCustomer(oauthParameters[OAuthBase.OAuthConsumerKeyKey]);
            string signature = string.Empty;
            string normalizedUrl = string.Empty;
            string normalizedRequestParameters = string.Empty;
            signature = this.GenerateSignature(uri, customer.ClientIdentifier, customer.ClientSecret, token, tokenSecret, "GET", oauthParameters[OAuthTimestampKey], oauthParameters[OAuthNonceKey], out normalizedUrl, out normalizedRequestParameters);
            if (oauthParameters[OAuthSignatureKey] != signature)
            {
                throw new ArgumentException("签名无效", OAuthSignatureKey);
            }
        }

        /*
         * Check for an OAuth Authorization HTTP header and, if present, parse it
         * and add it to the collection
         */
        private static NameValueCollection ParseAuthHeader(string authHeader)
        {
            if (!string.IsNullOrEmpty(authHeader))
            {
                NameValueCollection @params = new NameValueCollection();

                // Check for OAuth auth-scheme
                Match authSchemeMatch = OAuthCredentialsRegex.Match(authHeader);
                if (authSchemeMatch.Success)
                {
                    // We have OAuth credentials in the Authorization header; parse the parts
                    // Sad-to-say, but this code is much simpler than the regex for it!
                    string[] authParameterValuePairs = authHeader.Substring(authSchemeMatch.Length)
                        .Split(',');

                    foreach (string authParameterValuePair in authParameterValuePairs)
                    {
                        string[] parts = authParameterValuePair.Trim().Split('=');

                        if (parts.Length == 2)
                        {
                            string parameter = parts[0];
                            string value = parts[1];

                            if (value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal))
                            {
                                value = value.Substring(1, value.Length - 2);

                                try
                                {
                                    value = StringEscapeSequence.Replace(
                                        value,
                                        (Match match) =>
                                        {
                                            Group group = match.Groups[1];
                                            if (group.Length == 1)
                                            {
                                                switch (group.Value)
                                                {
                                                    case "\"": return "\"";
                                                    case "'": return "'";
                                                    case "\\": return "\\";
                                                    case "0": return "\0";
                                                    case "a": return "\a";
                                                    case "b": return "\b";
                                                    case "f": return "\f";
                                                    case "n": return "\n";
                                                    case "r": return "\r";
                                                    case "t": return "\t";
                                                    case "v": return "\v";
                                                }
                                            }

                                            return string.Format(
                                                CultureInfo.InvariantCulture,
                                                "{0}",
                                                char.Parse(group.Value));
                                        });
                                }
                                catch (FormatException)
                                {
                                    continue;
                                }

                                // Add the parameter and value
                                @params.Add(Rfc3986.Decode(parameter), Rfc3986.Decode(value));
                            }
                        }
                    }
                }
                return @params;
            }
            return null;
        }

        /// <summary>
        /// 获取参数异常的信息
        /// </summary>
        /// <param name="paramName">参数名称</param>
        /// <returns></returns>
        private string GetArgumentExceptionMessage(string paramName)
        {
            return string.Concat("缺少", paramName, "参数");
        }
    }
}
